// (C) Copyright 2012 Kystar. All rights reserved.

`timescale 1ns/100ps
`default_nettype none

module vib_wr_mem 
#(parameter
    FRAME1_START_ADDR = 0,
    FOR_EVEN_LINE = 0
)
(
    // global signals
    input  wire         I_sclk,
    input  wire         I_rst_n,

    // control
    input  wire          I_new_frame,
    input  wire          I_vib_use_buf1,

    // fifo interface
    output wire          O_fifo_rdreq,
    input  wire [ 31: 0] I_fifo_q,
    input  wire [ 12: 0] I_fifo_usedw,

    // mem interface
    output reg           O_sdram_req,
    input  wire          I_sdram_ack,
    input  wire          I_sdram_wr_rd_end,
    output reg           O_sdram_wr_en,
    output reg  [ 31: 0] O_sdram_start_addr,
    output reg  [ 15: 0] O_sdram_length,
    input  wire          I_sdram_ask_for_data,
    output wire [ 31: 0] O_sdram_wdata,

    // registers
    input  wire          I_reg_vib_enable,
    input  wire [ 11: 0] I_reg_vin_width,
    input  wire [ 11: 0] I_reg_vin_height,
    output wire          O_reg_rb_ddr3_err

);

/******************************************************************************
                                <localparams>
******************************************************************************/
localparam
    MAX_WR_LENGTH = 512;

localparam // wr_state
    WR_IDLE = 0,
    WR_INIT = 1,
    WR_LINE_PRE = 1<<1,
    WR_LINE = 1<<2,
    WR_LINE_POST = 1<<3,
    WR_IF_END = 1<<4;

localparam // wr_line_state
    WL_IDLE = 0,
    WL_INIT = 1,
    WL_WAIT_FIFO = 1<<1,
    WL_REQ_MEM = 1<<2,
    WL_START = 1<<3,
    WL_WRITING = 1<<4,
    WL_POST = 1<<5,
    WL_IF_FINISH = 1<<6,
    WL_END = 1<<7;

/******************************************************************************
                              <internal signals>
******************************************************************************/
reg  [ 4: 0] wr_state;
reg  [ 4: 0] next_wr_state;
reg  [ 11: 0] line_cnt;
reg  wr_line_start;
reg  [ 31: 0] line_start_addr;
reg  wr_line_end;
reg  [ 7: 0] wr_line_state;
reg  [ 7: 0] next_wr_line_state;
reg  [ 11: 0] left_pixel;
reg  [ 11: 0] current_length;
wire [ 11: 0] reg_line_word;
wire [ 11: 0] current_length_word;

/******************************************************************************
                                <module body>
******************************************************************************/
assign reg_line_word = {I_reg_vin_width[11:2],1'b0} + I_reg_vin_width[11:2];
assign current_length_word = {current_length[11:2],1'b0} + current_length[11:2];

//--------------------------------------------------------------------
// state machine : wr_state
//--------------------------------------------------------------------
always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        wr_state <= WR_IDLE;
    else if (!I_reg_vib_enable)
        wr_state <= WR_IDLE;
    else if (I_new_frame)
        wr_state <= WR_INIT;
    else
        wr_state <= next_wr_state;

always @(*)
    case (wr_state)
        WR_IDLE:
            next_wr_state = WR_IDLE;
        WR_INIT:
            next_wr_state = WR_LINE_PRE;
        WR_LINE_PRE:
            next_wr_state = WR_LINE;
        WR_LINE:
            if (wr_line_end)
                next_wr_state = WR_LINE_POST;
            else
                next_wr_state = WR_LINE;
        WR_LINE_POST:
            next_wr_state = WR_IF_END;
        WR_IF_END:
            if (line_cnt == I_reg_vin_height[11:1] + (FOR_EVEN_LINE == 1'b0 ? I_reg_vin_height[0] : 1'b0))
                next_wr_state = WR_IDLE;
            else
                next_wr_state = WR_LINE_PRE;

        default:
            next_wr_state = WR_IDLE;
    endcase

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        line_cnt <= 'd0;
    else if (I_new_frame)
        line_cnt <= 'd0;
    else if (wr_state == WR_IDLE)
        line_cnt <= 'd0;
    else if (wr_state == WR_LINE_POST)
        line_cnt <= line_cnt + 1'b1;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        wr_line_start <= 1'b0;
    else if (I_new_frame)
        wr_line_start <= 1'b0;
    else
        wr_line_start <= wr_state == WR_LINE_PRE;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        line_start_addr <= 'd0;
    else if (wr_state == WR_INIT)
        begin
        if (FOR_EVEN_LINE)
            line_start_addr <= (I_vib_use_buf1 ? FRAME1_START_ADDR : 0) + reg_line_word;
        else
            line_start_addr <= (I_vib_use_buf1 ? FRAME1_START_ADDR : 0);
        end
    else if (wr_state == WR_LINE_POST)
        line_start_addr <= line_start_addr + {reg_line_word,1'b0};

//--------------------------------------------------------------------
// state machine : wr_line_state
//--------------------------------------------------------------------
assign O_fifo_rdreq = I_sdram_ask_for_data & I_sdram_ack;

assign O_sdram_wdata = I_fifo_q;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        wr_line_state <= WL_IDLE;
    else if (I_new_frame)
        wr_line_state <= WL_IDLE;
    else
        wr_line_state <= next_wr_line_state;

always @(*)
    case (wr_line_state)
        WL_IDLE:
            if (wr_line_start)
                next_wr_line_state = WL_INIT;
            else
                next_wr_line_state = WL_IDLE;
        WL_INIT:
            next_wr_line_state = WL_WAIT_FIFO;
        WL_WAIT_FIFO:
            if (I_fifo_usedw >= current_length_word)
                next_wr_line_state = WL_REQ_MEM;
            else
                next_wr_line_state = WL_WAIT_FIFO;
        WL_REQ_MEM:
            if (I_sdram_ack)
                next_wr_line_state = WL_START;
            else
                next_wr_line_state = WL_REQ_MEM;
        WL_START:
            next_wr_line_state = WL_WRITING;
        WL_WRITING:
            if (I_sdram_wr_rd_end)
                next_wr_line_state = WL_POST;
            else
                next_wr_line_state = WL_WRITING;
        WL_POST:
            next_wr_line_state = WL_IF_FINISH;
        WL_IF_FINISH:
            if (left_pixel == 'd0)
                next_wr_line_state = WL_END;
            else
                next_wr_line_state = WL_WAIT_FIFO;
        WL_END:
            next_wr_line_state = WL_IDLE;
        default:
            next_wr_line_state = WL_IDLE;
    endcase

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        wr_line_end <= 1'b0;
    else
        wr_line_end <= wr_line_state == WL_END;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        left_pixel <= 'd0;
    else if (wr_line_start)
        left_pixel <= I_reg_vin_width;
    else if (wr_line_state == WL_START)
        left_pixel <= left_pixel - current_length;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        current_length <= 'd0;
    else if (wr_line_state == WL_INIT
        || wr_line_state == WL_POST)
        begin
        if (left_pixel >= MAX_WR_LENGTH)
            current_length <= MAX_WR_LENGTH;
        else
            current_length <= left_pixel;
        end

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        O_sdram_req <= 1'b0;
    else if (I_new_frame)
        O_sdram_req <= 1'b0;
    else if (wr_line_state == WL_REQ_MEM)
        O_sdram_req <= 1'b1;
    else if (wr_line_state == WL_POST)
        O_sdram_req <= 1'b0;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        O_sdram_wr_en <= 1'b0;
    else if (I_new_frame)
        O_sdram_wr_en <= 1'b0;
    else if (wr_line_state == WL_START)
        O_sdram_wr_en <= 1'b1;
    else if (I_sdram_wr_rd_end)
        O_sdram_wr_en <= 1'b0;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        O_sdram_start_addr <= 'd0;
    else if (wr_line_start)
        O_sdram_start_addr <= line_start_addr;
    else if (wr_line_state == WL_POST)
        O_sdram_start_addr <= O_sdram_start_addr + O_sdram_length;

always @(posedge I_sclk or negedge I_rst_n)
    if (!I_rst_n)
        O_sdram_length <= 'd0;
    else if (wr_line_state == WL_START)
        O_sdram_length <= current_length_word;

//--------------------------------------------------------------------
// O_reg_rb_ddr3_err
//--------------------------------------------------------------------
assign O_reg_rb_ddr3_err = 1'b0;

endmodule
`default_nettype wire

